﻿using System;
using System.IO;
using System.Xml;
using System.Reflection;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Collections.Generic;

using Builderator.Core.Action;
using Builderator.Core.Components;
using Builderator.Core.Interfaces;

namespace Builderator.Core
{
    public class Document : MarshalByRefObject, IXmlSerializable
    {
        public string Serialize()
        {
            try
            {
                string rootName = string.Empty;
                StringWriter writer = new StringWriter();
                XmlSerializer serializer = null;

                serializer = new XmlSerializer(this.GetType(), new XmlRootAttribute("Action"));
                
                using (XmlWriter xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings { OmitXmlDeclaration = true }))
                {
                    serializer.Serialize(xmlWriter, this);
                }

                return writer.ToString();

            }
            catch (Exception)
            {
                throw;
            }
        }

        public static object Deserialize(string settings, Type type)
        {
            try
            {
                StringReader reader = new StringReader(settings);
                XmlSerializer serializer = null;

                Type[] interfaces = type.FindInterfaces(new TypeFilter(
                    delegate(Type objType, object filterCriteria)
                    {
                        return (objType.Equals(filterCriteria)) ? true : false;
                    }), typeof(IDatabase));

                if (interfaces.Length > 0)
                {
                    serializer = new XmlSerializer(type, new XmlRootAttribute("Action"));
                }
                else
                {
                    serializer = new XmlSerializer(type, new XmlRootAttribute("License"));
                }

                return serializer.Deserialize(reader);
            }
            catch (Exception)
            {
                throw;
            }
        }

        public static object Deserialize(Type type)
        {
            return Activator.CreateInstance(type);
        }

        public XmlSchema GetSchema()
        {
            throw new NotImplementedException();
        }

        public void ReadXml(XmlReader reader)
        {
            try
            {
                object objectValue = null;
                string name = "", type = "";

                do
                {
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.Element:
                            {
                                if (reader.Name == "Action")
                                {
                                    for (int i = 0; i < reader.AttributeCount; i++)
                                    {
                                        reader.MoveToAttribute(i);

                                        switch (reader.Name)
                                        {
                                            case "Name":
                                                this.GetType().GetProperty(reader.Name).SetValue(this, reader.Value, null);
                                                break;
                                            case "Type":
                                            case "Identifier":
                                                this.GetType().GetProperty(reader.Name).SetValue(this, new Guid(reader.Value), null);
                                                break;
                                            default:
                                                break;
                                        }
                                    }
                                }
                                else
                                {
                                    bool element = false;

                                    for (int i = 0; i < reader.AttributeCount; i++)
                                    {
                                        reader.MoveToAttribute(i);

                                        switch (reader.Name)
                                        {
                                            case "Name":
                                                name = reader.Value;
                                                break;
                                            case "Type":
                                                type = reader.Value;
                                                break;
                                            default:
                                                break;
                                        }
                                    }

                                    reader.MoveToElement();

                                    switch (type)
                                    {
                                    case "Builderator.Core.List":
                                        {
                                            objectValue = new List(reader.ReadInnerXml());

                                            element = true;
                                        }
                                        break;
                                    case "Builderator.Core.TimeInterval":
                                        {
                                            objectValue = new TimeInterval(reader.ReadInnerXml());

                                            element = true;
                                        }
                                        break;
                                    case "Builderator.Core.VariableDictionary":
                                        {
                                            objectValue = new VariableDictionary(reader.ReadInnerXml());

                                            element = true;
                                        }
                                        break;
                                    case "Builderator.Core.Components.BuildVersion":
                                        {
                                            objectValue = new BuildVersion(reader.ReadInnerXml());

                                            element = true;
                                        }
                                        break;
                                    case "Builderator.Core.Components.ServerList":
                                        {
                                            objectValue = new ServerList(reader.ReadInnerXml());

                                            element = true;
                                        }
                                        break;
                                    case "Builderator.Core.JobStatus":
                                        {
                                            objectValue = (JobStatus)Enum.Parse(typeof(JobStatus), reader.ReadString());
                                        }
                                        break;
                                    case "System.Uri":
                                        {
                                            if (!reader.IsEmptyElement)
                                                objectValue = reader.ReadString();
                                        }
                                        break;
                                    case "System.Guid":
                                        {
                                            objectValue = new Guid(reader.ReadString());
                                        }
                                        break;
                                    case "System.DateTime":
                                        {
                                            objectValue = DateTime.FromBinary(long.Parse(reader.ReadString()));
                                        }
                                        break;
                                    case "System.String":
                                        {
                                            objectValue = reader.ReadString();
                                        }
                                        break;
                                    case "System.Boolean":
                                        {
                                            objectValue = Boolean.Parse(reader.ReadString());
                                        }
                                        break;
                                    case "System.Int32":
                                        {
                                            objectValue = Int32.Parse(reader.ReadString());
                                        }
                                        break;
                                    default:
                                        break;
                                    }

                                    if (objectValue != null)
                                    {
                                        this.GetType().GetProperty(name).SetValue(this, objectValue, null);

                                        objectValue = null;
                                    }

                                    if (element)
                                        goto case XmlNodeType.Element;
                                }
                            }
                            break;
                        case XmlNodeType.EndElement:
                            reader.Read();
                            break;
                        case XmlNodeType.None:
                            return;
                        default:
                            break;
                    }
                }
                while (reader.Read());
            }
            catch (Exception)
            {
                
            }
        }

        public void WriteXml(XmlWriter writer)
        {
            try
            {
                List<PropertyInfo> elementList = new List<PropertyInfo>();
                List<PropertyInfo> attributeList = new List<PropertyInfo>();

                foreach (PropertyInfo property in this.GetType().GetProperties())
                {
                    if (property.GetCustomAttributes(typeof(DocumentProperty), true).Length == 0)
                        continue;

                    switch (property.Name)
                    {
                        case "Name":
                        case "Type":
                        case "Identifier":
                            attributeList.Add(property);
                            break;
                        default:
                            elementList.Add(property);
                            break;
                    }
                }

                foreach (PropertyInfo property in attributeList)
                {
                    writer.WriteAttributeString(property.Name, property.GetValue(this, null).ToString());
                }

                object propertyValue;

                foreach (PropertyInfo property in elementList)
                {
                    writer.WriteStartElement("Property");

                    writer.WriteAttributeString("Name", property.Name);

                    writer.WriteAttributeString("Type", property.PropertyType.ToString());

                    propertyValue = property.GetValue(this, null);

                    if (property.PropertyType == typeof(DateTime))
                    {
                        writer.WriteRaw(((DateTime)propertyValue).ToBinary().ToString());
                    }
                    else
                    {
                        if (propertyValue != null)
                            writer.WriteRaw(propertyValue.ToString());
                        else
                            writer.WriteRaw(string.Empty);
                    }

                    writer.WriteEndElement();
                }
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}